﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Database = Red.Launcher.ViewModels.Workspaces.Database;

namespace Red.Launcher.Utilities.Dialog
{
    public enum CloseReason
    {
        Ok,
        Cancel
    }

    public delegate void InterfaceClosed();

    public interface IEvents
    {
        bool Propagated { get; set; }

        event InterfaceClosed Ok;
    }

    public interface IData
    {
        object Data { get; set; }
    }

    /// <summary>
    /// A unified interface for showing and hiding dialog windows and managing their lifetimes.
    /// </summary>
    public class Manager
    {
        public class DataContextObject<T>
        {
            public T Object { get; private set; }
            public Type Type { get; private set; }

            public object Get()
            {
                if ( Object != null )
                {
                    ICloneable clonableObject = Object as ICloneable;

                    if ( clonableObject != null )
                    {
                        return clonableObject.Clone();
                    }

                    return Object;
                }

                return Activator.CreateInstance( Type );
            }

            public static implicit operator DataContextObject<T>( T obj )
            {
                return new DataContextObject<T>()
                {
                    Object = obj
                };
            }

            public static implicit operator DataContextObject<T>( Type type )
            {
                return new DataContextObject<T>()
                {
                    Type = type
                };
            }
        }

        #region Private Data

        private Dictionary<string,Window> _dialogs = new Dictionary<string,Window>();

        #endregion Private Data

        #region Public Properties

        /// <summary>
        /// Used to ascertain if an administration dialog can be opened. If <see langword="null"/>, any dialog marked as admin will not open.
        /// </summary>
        public Database.User User { get; set; }
        public Window Owner { get; set; }

        #endregion Public Properties

        #region Delegates

        public delegate void Closed( IEvents dialog, CloseReason reason );

        #endregion Delegates

        #region Public Methods

        /// <summary>
        /// Show a dialog
        /// </summary>
        /// <typeparam name="DialogType">The type of the dialog to show</typeparam>
        /// <typeparam name="DataContextType">The type of the data context</typeparam>
        /// <param name="key">A key to uniquely identify this instance of the dialog. Defaults to the <see langword="string"/> name of the dialog type</param>
        /// <param name="dataContext">If not <see langword="null"/>, a clone of this object will be used as the datacontext when the dialog is created</param>
        /// <param name="callback">Called when the dialog is closed</param>
        /// <param name="isAdminDialog">If set to <see langword="true"/>, ensures that the dialog can only be opened by a user with the correct privilages</param>
        /// <param name="data">Some user data to be passed along to the dialog (needs to inherit from <see cref="IData"/>)</param>
        /// <returns>The dialog instance</returns>
        public void Show<DialogType, DataContextType>( string key, DataContextObject<DataContextType> dataContext = null, Closed callback = null, bool isAdminDialog = false, object data = null ) where DialogType : Window, new()
        {
            // If we have an invalid key, fall back to a valid one instead
            if ( String.IsNullOrEmpty( key ) )
            {
                key = typeof( DialogType ).Name;
            }

            // The user could potentially use a debugging tool like Snoop to reveal the hidden button
            // So lets also make sure to check to check if they're really an admin at this point
            bool canShowDialog = !isAdminDialog || ( User != null && User.IsAdmin );

            if ( canShowDialog )
            {
                Window dialog = null;

                if ( !_dialogs.TryGetValue( key, out dialog ) )
                {
                    dialog = new DialogType() { Owner = Owner };

                    if ( dataContext != null )
                    {
                        dialog.DataContext = dataContext.Get();
                    }

                    dialog.Show();

                    _dialogs.Add( key, dialog );

                    IEvents events = dialog as IEvents;
                    if ( events != null )
                    {
                        events.Ok += () =>
                        {
                            if ( callback != null )
                            {
                                callback( events, CloseReason.Ok );
                                events.Propagated = true;
                            }
                        };
                    }

                    IData dataDialog = dialog as IData;
                    if ( dataDialog != null )
                    {
                        dataDialog.Data = data;
                    }

                    dialog.Closed += ( object closedSender, EventArgs closedArgs ) =>
                    {
                        _dialogs.Remove( key );

                        // If the dialog implements IEvents, and if we haven't already called the callback due to OK being pressed
                        if ( events != null && !events.Propagated && callback != null )
                        {
                            callback( events, CloseReason.Cancel );
                            events.Propagated = true;
                        }
                    };
                }
                else
                {
                    dialog.Activate();
                }
            }
        }

        /// <summary>
        /// Show a dialog
        /// </summary>
        /// <typeparam name="DialogType">The type of the dialog to show</typeparam>
        /// <typeparam name="DataContextType">The type of the data context</typeparam>
        /// <param name="dataContext">If not <see langword="null"/>, a clone of this object will be used as the datacontext when the dialog is created</param>
        /// <param name="callback">Called when the dialog is closed</param>
        /// <param name="isAdminDialog">If set to <see langword="true"/>, ensures that the dialog can only be opened by a user with the correct privilages</param>
        /// <param name="data">Some user data to be passed along to the dialog (needs to inherit from <see cref="IData"/>)</param>
        /// <returns>The dialog instance</returns>
        public void Show<DialogType, DataContextType>( DataContextObject<DataContextType> dataContext, Closed callback = null, bool isAdminDialog = false, object data = null ) where DialogType : Window, new()
        {
            Show<DialogType, DataContextType>( typeof( DialogType ).Name, dataContext, callback, isAdminDialog, data );
        }

        /// <summary>
        /// Show a dialog
        /// </summary>
        /// <typeparam name="DialogType">The type of the dialog to show</typeparam>
        /// <param name="callback">Called when the dialog is closed</param>
        /// <param name="isAdminDialog">If set to <see langword="true"/>, ensures that the dialog can only be opened by a user with the correct privilages</param>
        /// <param name="data">Some user data to be passed along to the dialog (needs to inherit from <see cref="IData"/>)</param>
        /// <returns>The dialog instance</returns>
        public void Show<DialogType>( Closed callback = null, bool isAdminDialog = false, object data = null ) where DialogType : Window, new()
        {
            Show<DialogType, object>( typeof( DialogType ).Name, null, callback, isAdminDialog, data );
        }

        /// <summary>
        /// Closes all currently open dialogs
        /// </summary>
        public void CloseAll()
        {
            Window[] dialogs = _dialogs.Values.ToArray();

            foreach ( Window dialog in dialogs )
            {
                dialog.Close();
            }

            _dialogs.Clear();
        }

        #endregion Public Methods
    }
}
